using System;
using System.Collections.Generic;
using System.Threading;
using KukaDataComCommon;

namespace KukaDataCom
{
   public class SerialQueueHandler
   {
      #region Eigenschaften

      private readonly Queue<CommandJob> jobs = new Queue<CommandJob>();
      private readonly Modus[] modi;
      private readonly MySerialPort port;
      private readonly Thread transmissionThread;

      private volatile bool isRunning;

      public Modus[] Modi
      {
         get { return modi; }
      }

      public MySerialPort Port
      {
         get { return port; }
      }

      public bool IsRunning
      {
         get { return isRunning && Port.isOpen; }
         set { isRunning = value; }
      }

      /* altes IsRunning
      public bool IsRunning
      {
          get { return isRunning; }
          set
          {
              if (value != IsRunning)
              {
                  if (value)
                      isRunning = true;
                  else
                      isRunning = false;
              }
          }
      }
      */

      #endregion

      #region Konstruktor + Init

      public SerialQueueHandler()
      {
         port = new MySerialPort();
         transmissionThread = new Thread(runTransmission)
                                 {
                                    Name = "Transmission Thread",
                                    IsBackground = true
                                 };

         modi = getAvailableModi();

         RunTransmissionThread();
      }

      private static Modus[] getAvailableModi()
      {
         var result = new List<Modus>
                         {
                            new Modus('a', 1, 0, 6),
                            new Modus('b', 3, 0, 6),
                            new Modus('c', 1, 0, 10),
                            new Modus('g', 0, 0, 0),
                            new Modus('m', 5, 0, 0),
                            new Modus('n', 10, 0, 0),
                            new Modus('o', 15, 0, 0),
                            new Modus('p', 0, 5, 0),
                            new Modus('q', 0, 10, 0),
                            new Modus('r', 0, 15, 0),
                            new Modus('s', 0, 0, 10),
                            new Modus('t', 0, 0, 20),
                            new Modus('u', 0, 0, 40),
                            new Modus('v', 0, 0, 0),
                            new Modus('w', 0, 0, 0),
                            new Modus('x', 5, 5, 15),
                            new Modus('y', 10, 10, 30),
                            new Modus('z', 20, 20, 60)
                         };

         return result.ToArray();
      }

      #endregion

      #region public Methodes

      public void RunTransmissionThread()
      {
         if (!transmissionThread.IsAlive)
            transmissionThread.Start();
      }

      public void Enqueue(Modus modus, Int16[] ints, char[] chars, float[] floats)
      {
         Enqueue(new CommandJob(modus, ints, chars, floats));
      }

      public void Enqueue(CommandJob job)
      {
         if (job == null) throw new ArgumentNullException("job");

         jobs.Enqueue(job);
      }

      public Modus GetModus(char name)
      {
         foreach (Modus modus in modi)
            if (modus.Name == name)
               return modus;
         return null;
      }

      #endregion

      #region event

      //weitergeleitet durch den EventForwarder
      public event EventHandler<PositionUpdatedEventArgs> PositionUpdated;

      protected void OnPositionUpdated(Position position)
      {
         if (PositionUpdated != null)
            PositionUpdated(this, new PositionUpdatedEventArgs(position));
      }

      //weitergeleitet durch den EventForwarder
      public event EventHandler<AxisPositionUpdatedEventArgs> AxisPositionUpdated;

      protected void OnAxisPositionUpdated(AxisPosition position)
      {
         if (AxisPositionUpdated != null)
            AxisPositionUpdated(this, new AxisPositionUpdatedEventArgs(position));
      }

      public event EventHandler JobDequeued;
      public void Clear()
      {
         lock(this)
         {
            jobs.Clear();
         }
         
      }
      protected void OnJobDequeued()
      {
         if (JobDequeued != null)
            JobDequeued(this, new EventArgs());
      }

      #endregion

      #region serial Communication

      private void runTransmission()
      {
         while (true)
         {
            while (IsRunning)
            {
               try
               {
                  if(!listenAndTransmit())
                     Thread.Sleep(1000);
               }
               catch (TimeoutException)
               {
               }
            }

            while (!IsRunning)
               Thread.Sleep(100);
         }
      }

      private bool listenAndTransmit()
      {
         const char transmissionStartSequenz = 'A';
         const char packageConfirmationSequenz = 'b';
         const char transmissionConfirmationSequenz = 'z';

         if(!Port.WaitFor(transmissionStartSequenz))
            return false;
         if (jobs.Count == 0)
         {
            Port.Write('w');
            if (Port.ReadChar() != 'w')
            {
               //Error
               return false;
            }
         }
         else
         {
            CommandJob job = jobs.Peek();
            //OnJobDequeued();
            Port.Write(job.Modus.Name);
            char PortChar;
            if ((PortChar = Port.ReadChar()) != job.Modus.Name)
            {
               //Error
               return false;
            }
            jobs.Dequeue();
            OnJobDequeued();
            if (job.Modus.Name == 'v')
            {
               #region empfange PositionsDaten und feuere Event

               byte[] data = Port.ReadBytes(28);
               Position position;
               if (!Position.tryParse(data, out position))
               {
                  //Error
                  return false;
               }
               OnPositionUpdated(position);

               #endregion
            }
            else if (job.Modus.Name == 'g')
            {
               #region Empfange AxisPosition Daten

               byte[] data = Port.ReadBytes(24);
               AxisPosition position;
               if (!AxisPosition.tryParse(data, out position))
               {
                  //Error 
                  return false;
               }

               OnAxisPositionUpdated(position);

               #endregion
            }
            else
            {
               #region send data

               byte[][] data = job.toByteArray();
               for (int i = 0; i < data.Length; i++)
               {
                  Port.Write(data[i]);
                  if ((i + 1) < data.Length && Port.ReadChar() != packageConfirmationSequenz)
                  {
                     //Error
                     return false;
                  }
               }
               if (Port.ReadChar() != transmissionConfirmationSequenz)
               {
                  //Error
                  return false;
               }

               #endregion
            }
         }

         return true;
      }

      #endregion
   }
}